﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.IO;
using System.Threading.Tasks;

using Optimization.Experiment;
using Optimization.Utilities;

namespace Optimization.Optimizer
{
    class CommandExecutor
    {
        public CommandExecutor()
        {
        }

        private string[] pars;

        private string GetParameter(string name)
        {
            string result;

            result = pars.First((s) => (s.StartsWith(name)));
            result = result.Replace(name + "=", "");

            return result;
        }

        public void Run(string cmd, string[] pars)
        {
            this.pars = pars;
            switch (cmd)
            {
                case "listproblems":
                    CmdListProblems();
                    break;
                case "listsolvers":
                    CmdListSolvers();
                    break;
                case "execute":
                    CmdExecute();
                    break;
                case "compile":
                    CmdCompile();
                    break;
                case "exportcsv":
                    CmdExportCsv();
                    break;
                case "plantseeds":
                    CmdPlantSeeds();
                    break;
                case "definesteps":
                    CmdDefineSteps();
                    break;
                case "combine":
                    CmdCombine();
                    break;
                case "report":
                    foreach (string line in CmdMakeReport())
                        Console.WriteLine(line);
                    break;
                default:
                    Console.WriteLine("Unknown command");
                    break;
            }
        }

        private void CmdCombine()
        {
            string problemsFileName = GetParameter("-problems");
            string solversFileName = GetParameter("-solvers");
            string resultFileName = GetParameter("-output");

            ExperimentCollection problemsCol = null;
            FileStream fs = new FileStream(problemsFileName, FileMode.Open);
            try
            {
                problemsCol = ExperimentCollection.Create(fs);
            }
            finally
            {
                fs.Close();
            }
            ExperimentCollection solversCol = null;
            fs = new FileStream(solversFileName, FileMode.Open);
            try
            {
                solversCol = ExperimentCollection.Create(fs);
            }
            finally
            {
                fs.Close();
            }
            ExperimentCollection result = ExperimentDriver.Combine(problemsCol, solversCol);

            result.Save(resultFileName);
        }

        public IEnumerable<string> CmdMakeReport()
        {
            string xmlFileName = GetParameter("-input");

            ExperimentCollection input = null;
            FileStream fs = new FileStream(xmlFileName, FileMode.Open);
            try
            {
                input = ExperimentCollection.Create(fs);
            }
            finally
            {
                fs.Close();
            }

            return ExperimentDriver.MakeReport(input);
        }

        public void CmdDefineSteps()
        {
            string xmlFileName = GetParameter("-input");
            string resultFileName = GetParameter("-output");

            string stepsStr = GetParameter("-steps");
            if (stepsStr == "")
            {
                Console.WriteLine("Steps are not defined");
                return;
            }

            ExperimentCollection input = null;
            FileStream fs = new FileStream(xmlFileName, FileMode.Open);
            try
            {
                input = ExperimentCollection.Create(fs);
            }
            finally
            {
                fs.Close();
            }

            int[] steps = stepsStr.Split(';').Select((s) => int.Parse(s)).ToArray();

            ExperimentCollection result = ExperimentDriver.RedefineSteps(input, steps);

            result.Save(resultFileName);
        }

        /// <summary>
        /// Method which creates enumerator on random ints. Servers as random generator for seeds.
        /// </summary>
        /// <param name="count">Count of numbers in enumerator.</param>
        /// <returns></returns>
        IEnumerable<int> RandomInt(int count)
        {
            System.Random rnd = new System.Random();
            for (int i = 0; i < count; i++)
                yield return rnd.Next(1, 16777216);
        }

        private void CmdPlantSeeds()
        {
            string xmlFileName = GetParameter("-input");
            string resultFileName = GetParameter("-output");
            int seedCount = 1;
            int.TryParse(GetParameter("-seedcount"), out seedCount);

            ExperimentCollection input = null;
            FileStream fs = new FileStream(xmlFileName, FileMode.Open);
            try
            {
                input = ExperimentCollection.Create(fs);
            }
            finally
            {
                fs.Close();
            }

            int[] seeds = RandomInt(seedCount).ToArray();
            ExperimentCollection result = ExperimentDriver.RedefineSeeds(input, seeds);
            result.Save(resultFileName);
        }

        private void CmdExportCsv()
        {
            string xmlFileName = GetParameter("-input");
            string resultFileName = GetParameter("-output");

            ExperimentCollection input = null;
            FileStream fs = new FileStream(xmlFileName, FileMode.Open);
            try
            {
                input = ExperimentCollection.Create(fs);
            }
            finally
            {
                fs.Close();
            }

            StringBuilder result = new StringBuilder();
            foreach (ExperimentDescriptor ed in input)
                result.AppendLine(ed.ToString());

            File.WriteAllText(resultFileName, result.ToString());
        }

        private void CmdCompile()
        {
            throw new NotImplementedException();
        }

        private void CmdExecute()
        {
            string xmlFileName = GetParameter("-input");
            string resultFileName = GetParameter("-output");
            int domainCount = 1;
            int.TryParse(GetParameter("-parallelismlevel"), out domainCount);

            ExperimentCollection input = null;
            FileStream fs = new FileStream(xmlFileName, FileMode.Open);
            try
            {
                input = ExperimentCollection.Create(fs);
            }
            finally
            {
                fs.Close();
            }

            ExperimentCollection result = Execute(input, domainCount);
            result.Save(resultFileName);
        }

        private void CmdListSolvers()
        {
            string resultFileName = GetParameter("-output");

            ExperimentCollection result = ListSolvers();
            result.Save(resultFileName);
        }

        private void CmdListProblems()
        {
            string resultFileName = GetParameter("-output");

            ExperimentCollection result = ListProblems();
            result.Save(resultFileName);
        }
        /*
            Optimizer.exe -help
            Optimizer.exe -cmd=listproblems -output=problems.xml
            Optimizer.exe -cmd=listsolvers -output=solvers.xml
            Optimizer.exe -cmd=execute -input=experiment001.xml -output=experiment002.xml -parallelismlevel=8
            Optimizer.exe -cmd=compile -input=demoproblem.cs -output=demoproblem.dll
            Optimizer.exe -cmd=exportcsv -input=experiment001.xml -output=experiment001.csv
        */

        public ExperimentCollection ListProblems()
        {
            ExperimentCollection result = new ExperimentCollection();

            LibTester lt = new LibTester();
            Func<Type, bool> fnc = t => t.GetCustomAttributes(typeof(IsProblemAttribute), true).Any();

            string[] assembliesToTest = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll");
            List<string> problems = lt.Query(fnc, assembliesToTest);


            foreach (string problemName in problems)
            {
                ExperimentDescriptor ed = new ExperimentDescriptor();
                ed.Problem.Name = problemName;
                ed.Solver.Name = typeof(EmptySolver).AssemblyQualifiedName;
                ed.Seed = -1;

                result.Add(ed);
            }

            return result;
        }

        public ExperimentCollection ListSolvers()
        {
            ExperimentCollection result = new ExperimentCollection();

            LibTester lt = new LibTester();
            Func<Type, bool> fnc = t => t.GetCustomAttributes(typeof(IsSolverAttribute), true).Any();

            string[] assembliesToTest = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll");
            List<string> solvers = lt.Query(fnc, assembliesToTest);


            foreach (string solverName in solvers)
            {
                ExperimentDescriptor ed = new ExperimentDescriptor();
                ed.Problem.Name = typeof(EmptyProblem).AssemblyQualifiedName;
                ed.Solver.Name = solverName;
                ed.Seed = -1;

                result.Add(ed);
            }

            return result;
        }

        public ExperimentCollection Execute(ExperimentCollection ec, int domainCount = 1)
        {
            ExperimentCollection result = new ExperimentCollection();
            ExperimentDriver ed = new ExperimentDriver();
            result = ed.Execute(ec, domainCount);
            return result;
        }

        public void Compile(string csSource, string assemblyName)
        {
        }

    }
}
